![#SORACOM BeamでgRPCアプリのトラフィックをTLS暗号化する](https://devio2023-media.developers.io/wp-content/uploads/2015/09/soracom-logo-400x400.png)
#SORACOM BeamでgRPCアプリのトラフィックをTLS暗号化する
ども、大瀧です。
先日GAを迎えたGoogle製RPCフレームワークgRPCはマイクロサービスやモバイルアプリの文脈で紹介されることが多いですが、IoT用途でも使えないかなぁと思い立ち、SORACOM Beamとの組み合わせを考えてみました。
IoTでgRPCを利用するメリット
IoTシステムの開発では、センサーやIoTゲートウェイ(クライアントサイド)を開発する組み込みエンジニアとクラウド(サーバーサイド)を開発するWebエンジニアではスキルセットが異なることがままあると思います。クラウドとの通信はRESTful APIを利用することが多く、Web技術に明るくない組み込みエンジニアにとってHTTPのステートレスなAPIは敷居が高く感じられるかもしれません。
gRPCはWeb技術の中でも新しい部類で、仕組みとしてはHTTP/2を利用しますが、プログラミング言語に依存しないクライアント/サーバー間のインターフェース定義や複数の言語に対応するコードジェネレータがあり、通信部分の実装の多くをgRPCに任せることができます。また、認証やステートフル接続などgRPCが独自に提供する機能も豊富にあるので、エンジニアがセンサーデータの整形やアルゴリズムのコーディングに集中できるとも言えるでしょう。
SORACOM Beamの活用
gRPCは通信プロトコルとしてHTTP/2を採用し、TLSによる暗号化を前提としています。TLSは接続時の処理にCPUリソースを多く消費するため、頻繁に接続/切断する用途の場合、IoTゲートウェイ(gRPCクライアント)のリソースを多く使ってしまう恐れがあります。
SORACOM Beamは、3G/LTEモバイル網を利用するSORACOM Air SIMからのトラフィックに暗号化やヘッダ付与などの処理を施してサーバーに転送するプロキシサービスです。今回は、gRPCクライアント(暗号化なし)からの通信をBeamで暗号化し、TLSでListenしているgRPCサーバーに送信する構成をやってみます。
以前紹介した以下の記事と同様、インターネットを経由する箇所をセキュアにデータ転送させるための構成です。
検証環境
- クライアント : Macbook / macOS Sierra
- サーバー : Amazon EC2 / Amazon Linux 2016.09
- gRPC : grpc-go バージョン1.0.1-GA
検証手順
1. gRPCサーバーの構成
まずはEC2でgRPCのサンプルプログラムをビルド、実行します。今回はTLSでListenするので、サーバー証明書一式をCertbot(Let's EncryptのCLIツール)で取得しておき、以下のように実装しました。ハイライト行が変更・追加した部分です。
import ( "log" "net" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" pb "google.golang.org/grpc/examples/helloworld/helloworld" ) const ( port = ":50051" certFile = "./greeter_server/server.crt" keyFile = "./greeter_server/server.key" ) // server is used to implement helloworld.GreeterServer. type server struct{} // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { return &pb.HelloReply{Message: "Hello " + in.Name}, nil } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } //s := grpc.NewServer() var opts []grpc.ServerOption creds, err := credentials.NewServerTLSFromFile(certFile, keyFile) if err != nil { grpclog.Fatalf("Failed to generate credentials %v", err) } opts = []grpc.ServerOption{grpc.Creds(creds)} s := grpc.NewServer(opts...) pb.RegisterGreeterServer(s, &server{}) s.Serve(lis) }
$ sudo yum install -y go :(略) $ export GOPATH=~/.go $ go get golang.org/x/net/context google.golang.org/grpc $ cd .go/src/google.golang.org/grpc/examples/helloworld/ $ cp ~/{server.crt,server.key} greeter_server/ # あらかじめ作成した証明書をコピー $ go build -o greeterd greeter_server/main.go $ ./greeterd > greeterd.log 2>&1 & [1] 32077 $ sudo netstat -ltnp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 2278/rpcbind tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 2497/sshd tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 2527/sendmail tcp 0 0 0.0.0.0:35620 0.0.0.0:* LISTEN 2299/rpc.statd tcp 0 0 :::40334 :::* LISTEN 2299/rpc.statd tcp 0 0 :::111 :::* LISTEN 2278/rpcbind tcp 0 0 :::22 :::* LISTEN 2497/sshd tcp 0 0 :::50051 :::* LISTEN 32077/./greeterd $
50051番ポートをgreeterd
がListenしていればOKです。作成した証明書のホスト名に合わせて、EC2インスタンスのPublic IP(もしくはElastic IP)にDNSのAレコードを向けておきましょう。
2. SORACOM Beamの構成
続いて、使用するAir SIMが所属するグループをSORACOMユーザーコンソールで作成し、Beamの[TCP→TCP/TCPSエントリーポイント]を追加します。[転送先]設定に、[プロトコル]は「TCP(over TLS)」、[ホスト名]をDNSで登録したホスト名、[ポート番号]はサーバーがListenする「50051」をセットします。
クライアントの接続先は固定なので[エントリポイント]の各項目をメモしておきましょう。
3. gRPCクライアントの構成
サーバーと同じく、今回はGo版gRPCのサンプルプログラムを利用します。こちらは、接続先を上記のエントリポイントに設定するだけです。
package main import ( "log" "os" "golang.org/x/net/context" "google.golang.org/grpc" pb "google.golang.org/grpc/examples/helloworld/helloworld" ) const ( address = "beam.soracom.io:8023" defaultName = "world" ) func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. name := defaultName if len(os.Args) > 1 { name = os.Args[1] } r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name}) if err != nil { log.Fatalf("could not greet: %v", err) } log.Printf("Greeting: %s", r.Message) }
では、実行してみましょう。
$ go run greeter_client/main.go 2016/09/29 00:24:57 Greeting: Hello world $
gRPCサーバーから正常にレスポンスが返って来ました!
おまけ: ELBの組み合わせ
gRPCサーバーとELBの組み合わせをこちらの記事で紹介しましたが、Classic Load BalancerをL4転送として利用しているため、今回のTLS通信も転送することが可能です。DNSをELBに向けてTCP:50051
→TCP:50051
で転送設定しましょう。ELBのSSL TerminationはClassic Load BalancerがTLSのALPN拡張をサポートしないため不可、ということに注意しましょう。
まとめ
gRPCの変わったユースケースとして、IoT向けにSORACOM Beamとの組み合わせをご紹介しました。SORACOM BeamのTCPSエントリポイントがクライアント証明書をサポートするとgRPCの双方向認証に対応できそうなので、ちょっと期待したいところです。